/**
 * @author nttdocomo
 */
define(function() {
	aries.klass("aries.util.AbstractMixedCollection", Backbone.Collection.extend({
		initialize : function(allowFunctions, keyFn) {
			var me = this;
			me.map = {};
			me.keys = [];
			me.length = 0;

			/**
			 * @event clear
			 * Fires when the collection is cleared.
			 */

			/**
			 * @event add
			 * Fires when an item is added to the collection.
			 * @param {Number} index The index at which the item was added.
			 * @param {Object} o The item added.
			 * @param {String} key The key associated with the added item.
			 */

			/**
			 * @event replace
			 * Fires when an item is replaced in the collection.
			 * @param {String} key he key associated with the new added.
			 * @param {Object} old The item being replaced.
			 * @param {Object} new The new item.
			 */
			/**
			 * @event remove
			 * Fires when an item is removed from the collection.
			 * @param {Object} o The item being removed.
			 * @param {String} key (optional) The key associated with the removed item.
			 */

			me.allowFunctions = allowFunctions === true;

			if (keyFn) {
				me.getKey = keyFn;
			}

			//me.mixins.observable.constructor.call(me);
		},
		/**
		 * Adds an item to the collection. Fires the {@link #event-add} event when complete.
		 * @param {String} key <p>The key to associate with the item, or the new item.</p>
		 * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
		 * or if the key of the stored items is in a property called <code><b>id</b></code>,
		 * the MixedCollection will be able to <i>derive</i> the key for the new item.
		 * In this case just pass the new item in this parameter.</p>
		 * @param {Object} o The item to add.
		 * @return {Object} The item added.
		 */
		/*add : function(key, obj) {
			var me = this, myObj = obj, myKey = key, old;

			if (arguments.length == 1) {
				myObj = myKey;
				myKey = me.getKey(myObj);
			}
			if ( typeof myKey != 'undefined' && myKey !== null) {
				old = me.map[myKey];
				if ( typeof old != 'undefined') {
					return me.replace(myKey, myObj);
				}
				me.map[myKey] = myObj;
			}
			me.generation++;
			me.length++;
			me.models.push(myObj);
			me.keys.push(myKey);
			//if (me.hasListeners.add) {
			me.trigger('add', me.length - 1, myObj, myKey);
			//}
			return myObj;
		},*/

		/**
		 * Removes all items from the collection.  Fires the {@link #event-clear} event when complete.
		 */
		clear : function() {
			var me = this;

			me.length = 0;
			me.items = [];
			me.keys = [];
			me.map = {};
			me.generation++;
			//if (me.hasListeners.clear) {
				me.trigger('clear');
			//}
		},

		/**
		 * Returns true if the collection contains the passed Object as a key.
		 * @param {String} key The key to look for in the collection.
		 * @return {Boolean} True if the collection contains the Object as a key.
		 */
		containsKey : function(key) {
			return typeof this.map[key] != 'undefined';
		},

		/**
		 * Filter by a function. Returns a <i>new</i> collection that has been filtered.
		 * The passed function will be called with each object in the collection.
		 * If the function returns true, the value is included otherwise it is filtered.
		 * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
		 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
		 * @return {Ext.util.MixedCollection} The new filtered collection
		 */
		filterBy : function(fn, scope) {
			var me = this, newMC = new this.constructor(), keys = me.keys, items = me.models, length = items.length, i;

			newMC.getKey = me.getKey;

			for ( i = 0; i < length; i++) {
				if (fn.call(scope || me, items[i], keys[i])) {
					newMC.add(keys[i], items[i]);
				}
			}

			return newMC;
		},

		/**
		 * Returns the number of items in the collection.
		 * @return {Number} the number of items in the collection.
		 */
		getCount : function() {
			return this.length;
		},

		/**
		 * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
		 * simply returns <b><code>item.id</code></b> but you can provide your own implementation
		 * to return a different value as in the following examples:<pre><code>
		 // normal way
		 var mc = new Ext.util.MixedCollection();
		 mc.add(someEl.dom.id, someEl);
		 mc.add(otherEl.dom.id, otherEl);
		 //and so on

		 // using getKey
		 var mc = new Ext.util.MixedCollection();
		 mc.getKey = function(el){
		 return el.dom.id;
		 };
		 mc.add(someEl);
		 mc.add(otherEl);

		 // or via the constructor
		 var mc = new Ext.util.MixedCollection(false, function(el){
		 return el.dom.id;
		 });
		 mc.add(someEl);
		 mc.add(otherEl);
		 * </code></pre>
		 * @param {Object} item The item for which to find the key.
		 * @return {Object} The key for the passed item.
		 */
		getKey : function(o) {
			return o.id;
		},
		/**
		 * Returns a range of items in this collection
		 * @param {Number} startIndex (optional) The starting index. Defaults to 0.
		 * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
		 * @return {Array} An array of items
		 */
		getRange : function(start, end) {
			return this.toArray()
		},

		/**
		 * Returns index within the collection of the passed key.
		 * @param {String} key The key to find the index of.
		 * @return {Number} index of the key.
		 */
		indexOfKey : function(key) {
			return _.indexOf(this.keys, key);
		},
		//</deprecated>

		/**
		 * Inserts an item at the specified index in the collection. Fires the {@link #event-add} event when complete.
		 * @param {Number} index The index to insert the item at.
		 * @param {String} key The key to associate with the new item, or the item itself.
		 * @param {Object} o (optional) If the second parameter was a key, the new item.
		 * @return {Object} The item inserted.
		 */
		insert : function(index, key, obj) {
			var me = this, myKey = key, myObj = obj;

			if (arguments.length == 2) {
				myObj = myKey;
				myKey = me.getKey(myObj);
			}
			if (me.containsKey(myKey)) {
				me.suspendEvents();
				me.removeAtKey(myKey);
				me.resumeEvents();
			}
			if (index >= me.length) {
				return me.add(myKey, myObj);
			}
			me.generation++;
			me.length++;
			me.models.splice(index, 0, myObj);
			//Ext.Array.splice(me.models, index, 0, myObj);
			if ( typeof myKey != 'undefined' && myKey !== null) {
				me.map[myKey] = myObj;
			}
			me.keys.splice(index, 0, myKey);
			//Ext.Array.splice(me.keys, index, 0, myKey);
			//if (me.hasListeners.add) {
			me.trigger('add', index, myObj, myKey);
			//}
			return myObj;
		},

		/**
		 * Replaces an item in the collection. Fires the {@link #event-replace} event when complete.
		 * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
		 * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
		 * of your stored items is in a property called <code><b>id</b></code>, then the MixedCollection
		 * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
		 * with one having the same key value, then just pass the replacement item in this parameter.</p>
		 * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
		 * with that key.
		 * @return {Object}  The new item.
		 */
		replace : function(key, o) {
			var me = this, old, index;

			if (arguments.length == 1) {
				o = arguments[0];
				key = me.getKey(o);
			}
			old = me.map[key];
			if ( typeof key == 'undefined' || key === null || typeof old == 'undefined') {
				return me.add(key, o);
			}
			me.generation++;
			index = me.indexOfKey(key);
			me.models[index] = o;
			me.map[key] = o;
			me.trigger('replace', key, old, o);
			return o;
		}
	}))
})
